package com.ponhu.ea2.stockcenter.provider.service.biz.stockwarehouse;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.ponhu.ea2.stockcenter.dto.WMSEventInfoDTO;
import com.ponhu.ea2.stockcenter.dto.WMSInventoryInfo;
import com.ponhu.ea2.stockcenter.enums.ExchangeActionTypeEnum;
import com.ponhu.ea2.stockcenter.provider.constants.CommonConstant;
import com.ponhu.ea2.stockcenter.provider.entity.*;
import com.ponhu.ea2.stockcenter.provider.service.biz.stocksales.SalesLevelStockResponseService;
import com.ponhu.ea2.stockcenter.provider.service.entity.*;
import com.ponhu.ea2.stockcenter.provider.support.enums.StockChangeInnerEventEnum;
import com.ponhu.ea2.stockcenter.provider.support.exception.StockRuntimeException;
import com.ponhu.ea2.stockcenter.provider.support.service.RabbitMQService;
import com.ponhu.ea2.stockcenter.provider.support.service.RedisSupportService;
import com.ponhu.ea2.stockcenter.provider.support.vo.InventoryInfoVO;
import com.ponhu.ea2.stockcenter.provider.utils.CommonUtils;
import com.ponhu.ea2.stockcenter.provider.utils.DingTalkHandler;
import com.ponhu.ea2.stockcenter.provider.utils.RedisUtils;
import com.ponhu.ea2.stockcenter.vo.DingTalkContentVO;
import com.ponhu.ea2.stockcenter.vo.ReturnResponseBean;
import com.ponhu.ea2.stockcenter.vo.StockMqVO;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ponhu.ea2.stockcenter.enums.ExchangeActionTypeEnum.*;
import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.*;
import static com.ponhu.ea2.stockcenter.provider.constants.CommonConstant.RECORD_CHANNEL.WMS;

@Slf4j
public abstract class AbsCoreHandler {

    // 仓库库存变动对销售库存影响类型枚举集合
    protected ExchangeActionTypeEnum[] whActionTypeArr;
    // 当前仓库 true -> inWarehouse  |  false -> outWarehouse
    protected Boolean operateWarehouse;
    // 当前类型是否产生链路占用
    protected Boolean inOccupy;

    private static final List<ExchangeActionTypeEnum> outEnumList = Arrays.asList(
            ExchangeActionTypeEnum.SALES_RETURN_CANCLE_OUT,
            ExchangeActionTypeEnum.PURCHASE_CANCEL_OUT,
            ExchangeActionTypeEnum.PURCHASE_RETURN_OUT,
            ExchangeActionTypeEnum.CHECK_LOSS_OUT,
            ExchangeActionTypeEnum.OTHER_OUT,
            ExchangeActionTypeEnum.SALES_OUT);

    @Autowired
    private FastThreadLocal<String> traceIdLocal;
    @Autowired // 当前请求是否存在销售侧库存占用(销售订单占用或采退单占用)
    protected FastThreadLocal<Boolean> isSaleOccupyLocal;
    @Autowired // 获取请求参数Map(key: 货品编码，value: 仓库变动数量)
    protected FastThreadLocal<Map<String, Integer>> inventoryLocal;
    @Autowired // 库存变动动作配置信息
    private FastThreadLocal<StockWarehouseChangeSettings> settingsLocal;
    @Autowired // 货品仓库库存信息
    protected FastThreadLocal<List<StockWarehouseRelation>> whStocksLocal;
    @Autowired // 当前请求类型对销售侧库存影响的类型枚举
    protected FastThreadLocal<StockChangeInnerEventEnum> saleEventLocal;
    @Autowired // 当前请求类型对销售侧库存影响的类型枚举
    protected FastThreadLocal<List<StockWarehouseChange>> whChangesLocal;
    @Autowired // 请求部分失败异常
    protected FastThreadLocal<StockRuntimeException> stockRuntimeExceptionLocal;

    @Autowired
    protected AbsUtilHandler absUtilHandler;
    @Autowired
    private DingTalkHandler dingTalkHandler;
    @Autowired
    protected AbsAssistHandler absAssistHandler;
    @Autowired
    private RedisSupportService redisSupportService;
    @Autowired
    protected ConcurrentHashMap<ExchangeActionTypeEnum, AbsCoreHandler> handlerPool;

    @Autowired
    protected StockInventoryRealService stockInventoryRealService;
    @Autowired
    protected StockWarehouseChangeService stockWarehouseChangeService;
    @Autowired
    protected StockWarehouseRelationService stockWarehouseRelationService;
    @Autowired
    protected SalesLevelStockResponseService salesLevelStockResponseService;
    @Autowired
    protected StockInventoryDocOccupyService stockInventoryDocOccupyService;
    @Autowired
    protected StockInventoryBatchRecordService stockInventoryBatchRecordService;
    @Autowired
    protected StockInventoryBatchChangeService stockInventoryBatchChangeService;
    @Autowired
    protected StockInventoryWarehouseTraceService stockInventoryWarehouseTraceService;
    @Autowired
    protected StockWarehouseChangeSettingsService stockWarehouseChangeSettingsService;

    /**
     * 构造方法后将具体Handler加入handlerPool
     */
    @PostConstruct
    private void addPool() {
        for (ExchangeActionTypeEnum actionType : whActionTypeArr) {
            handlerPool.put(actionType, this);
        }
    }

    /**
     * 操作初始化之前补充请求参数
     */
    public void preThreadParamInit(WMSEventInfoDTO eventInfoDTO) {

        // 调拨申请，取消调拨申请、调拨出库、调拨入库需要预校验
        List<ExchangeActionTypeEnum> typeEnumList = Arrays.asList(
                TRANSFER_APPLY_CANCEL, TRANSFER_APPLY,
                TRANSFER_OUT, TRANSFER_IN);
        if (!typeEnumList.contains(eventInfoDTO.getActionType())) return;

        // 补充请求参数前的预校验
        absAssistHandler.preInitCheck(eventInfoDTO, typeEnumList);

        // 若当前请求是调拨申请，则不需要补充请求参数
        if (TRANSFER_APPLY == eventInfoDTO.getActionType()) return;

        // 补充调拨申请相关请求参数：调拨出库、调拨入库、取消调拨申请
        absAssistHandler.dtoSupply(eventInfoDTO);
    }

    /**
     * 对外暴露方法，由业务方调用
     */
    public ReturnResponseBean handle(WMSEventInfoDTO eventInfoDTO) {
        try {
            log.info("ThreadLocalInit ===>> traceId[{}] ===>> currentThread={}，isSaleOccupyLocal={}，inventoryLocal={}，" +
                            "settingsLocal={}，whStocksLocal={}，saleEventLocal={}，whChangesLocal={}, stockRuntimeExceptionLocal={}",
                    traceIdLocal.get(), Thread.currentThread().getName(), isSaleOccupyLocal.get(), inventoryLocal.get(),
                    settingsLocal.get(), whStocksLocal.get(), saleEventLocal.get(), whChangesLocal.get(), stockRuntimeExceptionLocal.get());

            // 初始化请求数据
            threadParamInit(eventInfoDTO);

            // 调用具体Handler实现的抽象方法
            absHandle(eventInfoDTO);

            // 当前请求不是任务链中的请求
            if (CommonUtils.isEmpty(eventInfoDTO.getDocNo()))
                return ReturnResponseBean.ok();

            // 记录任务链变动和关闭任务链需要并发互斥
            String redisKey = RedisUtils.getRedisKey(CommonConstant.REDISSON_RW_LOCK_TRACE_UPD, eventInfoDTO.getDocNo());
            ReturnResponseBean returnResponseBean = redisSupportService.wrapWithReadLock(redisKey, () -> traceChangeRecord(eventInfoDTO));

            // 记录货品出入库的开始时间和结束时间
            CompletableFuture.runAsync(() -> absAssistHandler.whInOutRecord(eventInfoDTO));

            return returnResponseBean;
        } finally {
            // 清除当前线程的ThreadLocal数据
            threadLocalClean();
        }
    }

    /**
     * 抽象方法，由具体Handler实现
     */
    protected abstract void absHandle(WMSEventInfoDTO eventInfoDTO);

    /**
     * 清除当前线程的ThreadLocal数据
     */
    private void threadLocalClean() {
        String traceId = traceIdLocal.get();

        traceIdLocal.remove();
        settingsLocal.remove();
        // saleEventLocal.remove();
        whStocksLocal.remove();
        // inventoryLocal.remove();
        whChangesLocal.remove();
        isSaleOccupyLocal.remove();
        stockRuntimeExceptionLocal.remove();

        log.info("ThreadLocalClean ===>> traceId[{}] ===>> currentThread={}，traceIdLocal={}，" +
                        "isSaleOccupyLocal={}，inventoryLocal={}，settingsLocal={}，whStocksLocal={}，" +
                        "saleEventLocal={}，whChangesLocal={}, stockRuntimeExceptionLocal={}",
                traceId, Thread.currentThread().getName(), traceIdLocal.get(), isSaleOccupyLocal.get(), inventoryLocal.get(),
                settingsLocal.get(), whStocksLocal.get(), saleEventLocal.get(), whChangesLocal.get(), stockRuntimeExceptionLocal.get());
    }

    /**
     * 记录WMS任务链执行时货品在仓库间变动
     */
    protected ReturnResponseBean traceChangeRecord(WMSEventInfoDTO eventInfoDTO) {

        String now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        List<StockInventoryWarehouseTrace> traceList = stockInventoryWarehouseTraceService
                .getWarehouseTraces(eventInfoDTO.getDocNo(), inventoryLocal.get().keySet());
        if (CollectionUtils.isEmpty(traceList)) {
            traceList = eventInfoDTO.getInventoryInfoList().stream().map(ivt ->
                    new StockInventoryWarehouseTrace()
                            .setUpdatedNum(ivt.getInventoryQuantity())  // 此处保持原正负值
                            .setInventoryCode(ivt.getInventoryCode())
                            .setDirectSn(eventInfoDTO.getDocNo())
                            .setRemark(String.format("%s [%s] 开启任务链 || \n", now, eventInfoDTO.getActionType().getCode()))
            ).collect(Collectors.toList());
        }

        String warehouseCode = operateWarehouse ?
                eventInfoDTO.getInWarehouseCode() : eventInfoDTO.getOutWarehouseCode();
        Boolean warehouseSale = operateWarehouse ?
                eventInfoDTO.getInWarehouseIsSale() : eventInfoDTO.getOutWarehouseIsSale();

        // 校验当前任务链路是否已经关闭
        String redisKey = RedisUtils.getRedisKey(CommonConstant.REDISSON_RW_LOCK_TRACE_DONE, eventInfoDTO.getDocNo());
        String traceDownTime = redisSupportService.bucketGetAndDel(redisKey, String.class);

        traceList.stream().forEach(trace -> {
            if (eventInfoDTO.getActionType().getCode().equals(trace.getActionType())) {
                Integer inventoryQuantity = inventoryLocal.get().getOrDefault(trace.getInventoryCode(), 0);
                trace.setUpdatedNum(trace.getUpdatedNum() + inventoryQuantity); // 此处保持原正负值
            } else {
                trace.setUpdatedNum(inventoryLocal.get().get(trace.getInventoryCode())); // 此处保持原正负值
            }

            trace.setInOccupy(inOccupy)
                    .setWarehouseSale(warehouseSale)
                    .setWarehouseCode(warehouseCode)
                    .setUpdateTime(LocalDateTime.now())
                    .setTransferNo(eventInfoDTO.getTransferNo())
                    .setActionType(eventInfoDTO.getActionType().getCode())
                    .setOptRemark(eventInfoDTO.getActionType().getName());

            // 校验当前任务链路是否已经关闭
            if (StringUtils.isNotEmpty(traceDownTime)) {
                trace.setInProgress(false)
                        .setRemark(String.format("%s 关闭任务链 || \n%s", traceDownTime, trace.getRemark()));
            }

            // 调拨出库、调拨入库等是由巨沃回调发起，关闭任务链是由WMS发起，所以会有时序问题
            // 调拨申请和关闭任务链都是由WMS发起，所以不会由时序问题
            // 调拨申请默认为任务链路的开启(取消订单时复用订单履约时的调用链路记录)
            if (TRANSFER_APPLY == eventInfoDTO.getActionType()) {
                if (BooleanUtils.isFalse(trace.getInProgress()))
                    trace.setRemark(String.format("%s [%s] 开启任务链 || \n%s", now, eventInfoDTO.getActionType().getCode(), trace.getRemark()));
                trace.setInProgress(true);
            }
        });

        if (!stockInventoryWarehouseTraceService.saveOrUpdateBatch(traceList))
            throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900108);

        // 操作成功
        return ReturnResponseBean.ok();
    }

    /**
     * 操作初始化查询
     */
    protected void threadParamInit(WMSEventInfoDTO eventInfoDTO) {

        // 获取仓库变动动作配置信息
        ExchangeActionTypeEnum actionType = eventInfoDTO.getActionType();
        StockWarehouseChangeSettings settings = stockWarehouseChangeSettingsService
                .getWarehouseChangeSettingsByType(WMS, actionType.getCode());
        settingsLocal.set(settings);

        // 根据请求的货品列表获取对应货品当前仓库库存信息
        List<WMSInventoryInfo> inventoryInfoList = eventInfoDTO.getInventoryInfoList();
        List<String> inventoryCodeList = inventoryInfoList.stream()
                .map(WMSInventoryInfo::getInventoryCode).collect(Collectors.toList());
        // 当前操作仓库编码
        String warehouseCode = BooleanUtils.isTrue(operateWarehouse) ?
                eventInfoDTO.getInWarehouseCode() : eventInfoDTO.getOutWarehouseCode();
        List<StockWarehouseRelation> stockWarehouseRelations = stockWarehouseRelationService
                .getStockWarehouseRelations(inventoryCodeList, warehouseCode);
        whStocksLocal.set(stockWarehouseRelations);

        // 将请求货品编码列表转化为map(key:货品编码，value:变动数量)，方便后续使用
        Map<String, Integer> inventoryInfoMap = eventInfoDTO.getInventoryInfoList()
                .stream().collect(Collectors.toMap(WMSInventoryInfo::getInventoryCode,
                        WMSInventoryInfo::getInventoryQuantityAbs, Integer::sum));
        inventoryLocal.set(inventoryInfoMap);

        // 根据当前请求类型确定对销售侧库存影响的类型枚举
        absAssistHandler.saleEventInit(eventInfoDTO, operateWarehouse);
    }

    /**
     * 任务链路中的仓库库存变动预校验
     */
    protected void preTraceCheck(WMSEventInfoDTO eventInfoDTO) {

        // 当前请求不是任务链中的请求
        if (CommonUtils.isEmpty(eventInfoDTO.getDocNo())) return;

        List<StockInventoryWarehouseTrace> traceList = stockInventoryWarehouseTraceService
                .getWarehouseTraces(eventInfoDTO.getDocNo(), inventoryLocal.get().keySet());
        if (CollectionUtils.isEmpty(traceList)) return;

        // 任务链路中的不同类型动作操作库存数要保持一致
        StringBuffer msg = new StringBuffer();
        absUtilHandler.ListAllMacth(traceList, trace -> {
            if (eventInfoDTO.getActionType().getCode().equals(trace.getActionType())) return true;

            Integer inventoryQuantity = inventoryLocal.get().getOrDefault(trace.getInventoryCode(), Integer.MIN_VALUE);
            boolean macth = trace.getUpdatedNumAbs() >= inventoryQuantity;
            if (BooleanUtils.isNotTrue(macth))
                msg.append(String.format(" 货品编码-%s:{链路占用库存数(%s) >= 调拨请求库存数(%s)}校验失败；",
                        trace.getInventoryCode(), trace.getUpdatedNumAbs(), inventoryQuantity));
            return macth;
        }, STOCK_WAREHOUSE_EXCEPTION_900114, msg);
    }

    /**
     * 通用仓库出库库存校验
     */
    protected void commonOutStockCheck(WMSEventInfoDTO eventInfoDTO) {

        // 校验出库货品是否存在当前仓库
        absUtilHandler.hasOperateInventory(eventInfoDTO);

        // 当前操作仓库是否可售
        Boolean warehouseIsSale = operateWarehouse ?
                eventInfoDTO.getOutWarehouseIsSale() : eventInfoDTO.getOutWarehouseIsSale();

        // 可售仓出库库存校验
        if (BooleanUtils.isTrue(warehouseIsSale)) {
            absAssistHandler.commonSaleOutStockCheck(eventInfoDTO, operateWarehouse);
        }

        //不可售仓出库库存校验
        if (BooleanUtils.isFalse(warehouseIsSale)) {
            absAssistHandler.commonNoSaleOutStockCheck(eventInfoDTO, operateWarehouse);
        }
    }

    /**
     * 仓库库存变动数据库操作
     */
    protected void stockWarehouseHandle(WMSEventInfoDTO eventInfoDTO) {

        List<StockWarehouseRelation> stockWarehouseRelations = whStocksLocal.get();
        // 请求调拨的货品种类和当前仓库的对应的货品种类数量不一致，说明当前仓库有的货品库存记录还没有初始化
        if (!outEnumList.contains(eventInfoDTO.getActionType()) &&
                stockWarehouseRelations.size() != eventInfoDTO.getInventoryInfoList().size()) {
            // 获取货品信息集合并转Map(key：货品编码，value：货品ID)
            Map<String, Long> stockMap = stockInventoryRealService.getRealInventoryStockListIfNullCreate(inventoryLocal.get().keySet())
                    .stream().collect(Collectors.toMap(StockInventoryReal::getInventoryCode, StockInventoryReal::getInventoryId));
            // 有的调拨货品不存在
            if (stockMap.size() != eventInfoDTO.getInventoryInfoList().size()) {
                String codes = eventInfoDTO.getInventoryInfoList().stream().map(WMSInventoryInfo::getInventoryCode)
                        .filter(code -> ObjectUtils.isEmpty(stockMap.get(code))).collect(Collectors.joining(", "));
                throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900106, codes);
            }

            // 将货品仓库库存信息集合转Map(key：货品编码，value：仓库库存信息)
            Map<String, StockWarehouseRelation> relationMap = stockWarehouseRelations.stream()
                    .collect(Collectors.toMap(StockWarehouseRelation::getInventoryCode, Function.identity()));
            // 请求调入的货品再当前仓库没有则进行初始化
            eventInfoDTO.getInventoryInfoList().stream().forEach(info -> {
                if (relationMap.get(info.getInventoryCode()) == null) {
                    StockWarehouseRelation relation = new StockWarehouseRelation()
                            .setInventoryId(stockMap.get(info.getInventoryCode()))
                            .setInventoryCode(info.getInventoryCode())
                            .setWarehouseCode(eventInfoDTO.getInWarehouseCode())
                            .setRealStockNum(0)
                            .setAvailableStockNum(0)
                            .setLockedStockNum(0);
                    stockWarehouseRelations.add(relation);
                }
            });
        }

        // 解析仓库库存动态类型
        absUtilHandler.actionHandler(stockWarehouseRelations);

        // 批量操作仓库库存(插入或修改)
        if (!stockWarehouseRelationService.saveOrUpdateBatch(stockWarehouseRelations))
            throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900101);


        // 批量保存仓库库存变动记录和仓库快照
        List<StockWarehouseChange> changeList = stockWarehouseChangeService
                .saveBatchChanges(stockWarehouseRelations, eventInfoDTO);

        // 校验是否是批次库存变动
        Boolean anyMatch = eventInfoDTO.getInventoryInfoList().stream()
                .anyMatch(info -> StringUtils.isNotEmpty(info.getBatchCode()));
        if (BooleanUtils.isTrue(anyMatch)) {
            // 查询请求货品批次库存信息
            List<StockInventoryBatchRecord> batchRecordList = stockInventoryBatchRecordService
                    .getBatchRecordList(eventInfoDTO.getInventoryInfoList());

            // 批量保存批次库存变动记录
            batchRecordList = stockInventoryBatchChangeService.saveBatchChanges(changeList, eventInfoDTO, batchRecordList);

            // 批量保存批次库存
            if (!stockInventoryBatchRecordService.saveOrUpdateBatch(batchRecordList))
                throw new StockRuntimeException(STOCK_WAREHOUSE_EXCEPTION_900117);

            // 若批次库存出现负数则钉钉报警
            /*String traceId = traceIdLocal.get();
            List<StockInventoryBatchRecord> finalBatchRecordList = batchRecordList;
            CompletableFuture.runAsync(() -> {
                String msg = finalBatchRecordList.stream().filter(record -> record.getBatchNum() < 0)
                        .map(record -> String.format("货品编码[%s] - 批次编码[%s] - 批次库存[%s]",
                                record.getInventoryCode(), record.getBatchCode(), record.getBatchNum()))
                        .collect(Collectors.joining("；"));

                if (StringUtils.isNotEmpty(msg)) {
                    DingTalkContentVO dingTalkContentVO = new DingTalkContentVO()
                            .setPath("dubbo facade interface StockForWMSServiceImpl.receiveWMSEventHandler")
                            .setExceptionCode(STOCK_WAREHOUSE_EXCEPTION_900118.getCode().toString())
                            .setRequestParams(JSON.toJSONString(eventInfoDTO))
                            .setExceptionType("批次库存出现负数")
                            .setExceptionMsg(msg)
                            .setTraceId(traceId);
                    dingTalkHandler.sendMsg(dingTalkContentVO);
                }
            });*/
        }
    }

    /**
     * 仓库库存变动对销售库存影响
     */
    protected void salesStockHandle(WMSEventInfoDTO eventInfoDTO) {

        log.info("salesStockHandle ===>> traceId[{}] ===>> eventInfoDTO={}，currentThread={}，saleEventLocal={}",
                traceIdLocal.get(), eventInfoDTO, Thread.currentThread().getName(), saleEventLocal.get());

        // 当前仓库库存变动对销售库存无影响
        if (ObjectUtils.isEmpty(saleEventLocal.get())) return;

        // 构建销售库存变动参数
        List<InventoryInfoVO> inventoryInfos = inventoryLocal.get().entrySet()
                .parallelStream().map(entry ->
                        new InventoryInfoVO().setInventoryCode(entry.getKey())
                                .setInventoryQuantity(entry.getValue()))
                .collect(Collectors.toList());

        // 仓库库存变动对销售库存的影响
        if (!salesLevelStockResponseService.handleRealityLevelChangeEvent(
                eventInfoDTO.getDocNo(), saleEventLocal.get(), inventoryInfos)) {
            throw new StockRuntimeException(STOCK_SALES_EXCEPTION_900304);
        }
    }

    /**
     * 修改货品在途库存数量
     */
    protected void updateFreeStockNum(WMSEventInfoDTO eventInfoDTO) {

        boolean update = stockInventoryRealService
                .updateFreeStockNum(eventInfoDTO.getInventoryInfoList(), eventInfoDTO.getActionType());

        if (BooleanUtils.isNotTrue(update))
            throw new StockRuntimeException(STOCK_SALES_EXCEPTION_900306);
    }

}
